package access

import (
	"encoding/hex"
	"encoding/pem"
	"fmt"
	"strings"

	commonCert "chainmaker.org/chainmaker/common/v2/cert"
	bccrypto "chainmaker.org/chainmaker/common/v2/crypto"
	"chainmaker.org/chainmaker/common/v2/crypto/asym"
	"chainmaker.org/chainmaker/common/v2/crypto/hash"
	bcx509 "chainmaker.org/chainmaker/common/v2/crypto/x509"
	pbac "chainmaker.org/chainmaker/pb-go/v2/accesscontrol"
	"chainmaker.org/chainmaker/protocol/v2"

	nodeconfig "github.com/tokentransfer/node/config"
)

type organization struct {
	// Name of this group
	id string

	// Trusted certificates or white list
	trustedRootCerts map[string]*bcx509.Certificate

	// Trusted intermediate certificates or white list
	trustedIntermediateCerts map[string]*bcx509.Certificate
}

var _ protocol.Member = (*certificateMember)(nil)

// an instance whose member type is a public key
type certificateMember struct {

	// the CommonName field of the certificate
	id string

	// organization identity who owns this member
	orgId string

	// the X.509 certificate used for authentication
	cert *bcx509.Certificate

	// role of this member
	role protocol.Role

	// hash algorithm for chains (It's not the hash algorithm that the certificate uses)
	hashType string

	// the certificate is compressed or not
	isCompressed bool
}

// GetPk returns the public key
func (cm *certificateMember) GetPk() bccrypto.PublicKey {
	return cm.cert.PublicKey
}

// GetMemberId returns the identity of this member (non-uniqueness)
func (cm *certificateMember) GetMemberId() string {
	return cm.id
}

// GetOrgId returns the organization id which this member belongs to
func (cm *certificateMember) GetOrgId() string {
	return cm.orgId
}

// GetRole returns roles of this member
func (cm *certificateMember) GetRole() protocol.Role {
	return cm.role
}

// GetUid returns the identity of this member (unique)
func (cm *certificateMember) GetUid() string {
	return hex.EncodeToString(cm.cert.SubjectKeyId)
}

// Verify verifies a signature over some message using this member
func (cm *certificateMember) Verify(hashType string, msg []byte, sig []byte) error {
	hashAlgo, err := bcx509.GetHashFromSignatureAlgorithm(cm.cert.SignatureAlgorithm)
	if err != nil {
		return fmt.Errorf("cert member verify failed: get hash from signature algorithm failed: [%s]", err.Error())
	}
	ok, err := cm.cert.PublicKey.VerifyWithOpts(msg, sig, &bccrypto.SignOpts{
		Hash: hashAlgo,
		UID:  bccrypto.CRYPTO_DEFAULT_UID,
	})
	if err != nil {
		return fmt.Errorf("cert member verify signature failed: [%s]", err.Error())
	}
	if !ok {
		return fmt.Errorf("cert member verify signature failed: invalid signature")
	}
	return nil
}

// GetCertificateIdFromDER get cert id from cert der
func GetCertificateIdFromDER(certDER []byte, hashType string) ([]byte, error) {
	if certDER == nil {
		return nil, fmt.Errorf("get cert from der certDER == nil")
	}
	id, err := hash.GetByStrType(hashType, certDER)
	if err != nil {
		return nil, err
	}
	return id, nil
}

// GetMember returns Member
func (cm *certificateMember) GetMember() (*pbac.Member, error) {
	if cm.isCompressed {
		id, err := GetCertificateIdFromDER(cm.cert.Raw, cm.hashType)
		if err != nil {
			return nil, fmt.Errorf("get pb member failed: [%s]", err.Error())
		}
		return &pbac.Member{
			OrgId:      cm.orgId,
			MemberInfo: id,
			MemberType: pbac.MemberType_CERT_HASH,
		}, nil
	}

	certPEM := pem.EncodeToMemory(&pem.Block{Bytes: cm.cert.Raw, Type: "CERTIFICATE"})
	return &pbac.Member{
		OrgId:      cm.orgId,
		MemberInfo: certPEM,
		MemberType: pbac.MemberType_CERT,
	}, nil
}

//cryptoEngineOption parse public key by CryptoEngine
func cryptoEngineOption(cert *bcx509.Certificate) error {
	pkPem, err := cert.PublicKey.String()
	if err != nil {
		return fmt.Errorf("failed to get public key pem, err = %s", err)
	}
	cert.PublicKey, err = asym.PublicKeyFromPEM([]byte(pkPem))
	if err != nil {
		return fmt.Errorf("failed to parse public key, err = %s", err.Error())
	}
	return nil
}

func newCertMemberFromParam(orgId, role, hashType string, isCompressed bool,
	certPEM []byte) (*certificateMember, error) {
	var (
		cert *bcx509.Certificate
		err  error
	)
	certBlock, rest := pem.Decode(certPEM)
	if certBlock == nil {
		cert, err = bcx509.ParseCertificate(rest)
		if err != nil {
			return nil, fmt.Errorf("new cert member failed, invalid certificate")
		}
	} else {
		cert, err = bcx509.ParseCertificate(certBlock.Bytes)
		if err != nil {
			return nil, fmt.Errorf("new cert member failed, invalid certificate")
		}
	}

	if err = cryptoEngineOption(cert); err != nil {
		return nil, fmt.Errorf("set crypto engine failed, err = %s", err)
	}

	id, err := bcx509.GetExtByOid(bcx509.OidNodeId, cert.Extensions)
	if err != nil {
		id = []byte(cert.Subject.CommonName)
	}

	role = strings.ToUpper(role)

	return &certificateMember{
		id:           string(id),
		orgId:        orgId,
		role:         protocol.Role(role),
		cert:         cert,
		hashType:     hashType,
		isCompressed: isCompressed,
	}, nil
}

func newMemberFromCertPem(orgId, hashType string, certPEM []byte, isCompressed bool) (*certificateMember, error) {
	var member certificateMember
	member.isCompressed = isCompressed

	var cert *bcx509.Certificate
	var err error
	certBlock, rest := pem.Decode(certPEM)
	if certBlock == nil {
		cert, err = bcx509.ParseCertificate(rest)
		if err != nil {
			return nil, fmt.Errorf("new cert member failed, invalid certificate")
		}
	} else {
		cert, err = bcx509.ParseCertificate(certBlock.Bytes)
		if err != nil {
			return nil, fmt.Errorf("new cert member failed, invalid certificate")
		}
	}

	if err = cryptoEngineOption(cert); err != nil {
		return nil, fmt.Errorf("set crypto engine failed, err = %s", err)
	}

	member.hashType = hashType
	member.orgId = orgId

	orgIdFromCert := ""
	if len(cert.Subject.Organization) > 0 {
		orgIdFromCert = cert.Subject.Organization[0]
	}
	if member.orgId == "" {
		member.orgId = orgIdFromCert
	}
	if orgIdFromCert != member.orgId {
		return nil, fmt.Errorf(
			"setup cert member failed, organization information in certificate "+
				"and in input parameter do not match [certificate: %s, parameter: %s]",
			orgIdFromCert,
			orgId,
		)
	}

	id, err := bcx509.GetExtByOid(bcx509.OidNodeId, cert.Extensions)
	if err != nil {
		id = []byte(cert.Subject.CommonName)
	}
	member.id = string(id)
	member.cert = cert
	ou := ""
	if len(cert.Subject.OrganizationalUnit) > 0 {
		ou = cert.Subject.OrganizationalUnit[0]
	}
	ou = strings.ToUpper(ou)
	member.role = protocol.Role(ou)
	return &member, nil
}

func newCertMemberFromPb(member *pbac.Member, c *nodeconfig.Config) (*certificateMember, error) {
	if member.MemberType == pbac.MemberType_CERT {
		return newMemberFromCertPem(member.OrgId, c.NodeConfig.HashType, member.MemberInfo, false)
	}
	if member.MemberType == pbac.MemberType_CERT_HASH {
		return newMemberFromCertPem(member.OrgId, c.NodeConfig.HashType, member.MemberInfo, true)
	}
	if member.MemberType == pbac.MemberType_ALIAS {
		return newMemberFromCertPem(member.OrgId, c.NodeConfig.HashType, member.MemberInfo, false)
	}
	return nil, fmt.Errorf("setup member failed, unsupport cert member type")
}

type signingCertMember struct {
	// Extends Identity
	certificateMember

	// Sign the message
	sk bccrypto.PrivateKey
}

// Sign When using certificate, the signature-hash algorithm suite is from the certificate
// and the input hashType is ignored.
func (scm *signingCertMember) Sign(hashType string, msg []byte) ([]byte, error) {
	hashAlgo, err := bcx509.GetHashFromSignatureAlgorithm(scm.cert.SignatureAlgorithm)
	if err != nil {
		return nil, fmt.Errorf("sign failed: invalid algorithm: %s", err.Error())
	}

	return scm.sk.SignWithOpts(msg, &bccrypto.SignOpts{
		Hash: hashAlgo,
		UID:  bccrypto.CRYPTO_DEFAULT_UID,
	})
}

var _ protocol.Member = (*pkMember)(nil)

// an instance whose member type is a certificate
type pkMember struct {

	// pem public key
	id string

	// organization identity who owns this member
	orgId string

	// public key uid
	uid string

	// the public key used for authentication
	pk bccrypto.PublicKey

	// role of this member
	role protocol.Role

	// hash type from chain configuration
	hashType string
}

func (pm *pkMember) GetPk() bccrypto.PublicKey {
	return pm.pk
}

// GetMemberId returns the identity of this member (non-uniqueness)
func (pm *pkMember) GetMemberId() string {
	return pm.id
}

// GetOrgId returns the organization id which this member belongs to
func (pm *pkMember) GetOrgId() string {
	return pm.orgId
}

// GetRole returns roles of this member
func (pm *pkMember) GetRole() protocol.Role {
	return pm.role
}

// GetUid returns the identity of this member (unique)
func (pm *pkMember) GetUid() string {
	return pm.uid
}

// Verify verifies a signature over some message using this member
func (pm *pkMember) Verify(hashType string, msg []byte, sig []byte) error {

	hash, ok := bccrypto.HashAlgoMap[hashType]
	if !ok {
		return fmt.Errorf("cert member verify signature failed: unsupport hash type")
	}
	ok, err := pm.pk.VerifyWithOpts(msg, sig, &bccrypto.SignOpts{
		Hash: hash,
		UID:  bccrypto.CRYPTO_DEFAULT_UID,
	})
	if err != nil {
		return fmt.Errorf("cert member verify signature failed: [%s]", err.Error())
	}
	if !ok {
		return fmt.Errorf("cert member verify signature failed: invalid signature")
	}
	return nil
}

// GetMember returns Member
func (pm *pkMember) GetMember() (*pbac.Member, error) {
	memberInfo, err := pm.pk.String()
	if err != nil {
		return nil, fmt.Errorf("get pb member failed: %s", err.Error())
	}
	return &pbac.Member{
		OrgId:      pm.orgId,
		MemberInfo: []byte(memberInfo),
		MemberType: pbac.MemberType_PUBLIC_KEY,
	}, nil
}

type signingPKMember struct {
	// Extends Identity
	pkMember

	// Sign the message
	sk bccrypto.PrivateKey
}

// When using public key instead of certificate,
//hashType is used to specify the hash algorithm while the signature algorithm is decided by the public key itself.
func (spm *signingPKMember) Sign(hashType string, msg []byte) ([]byte, error) {
	hash, ok := bccrypto.HashAlgoMap[hashType]
	if !ok {
		return nil, fmt.Errorf("sign failed: unsupport hash type")
	}
	return spm.sk.SignWithOpts(msg, &bccrypto.SignOpts{
		Hash: hash,
		UID:  bccrypto.CRYPTO_DEFAULT_UID,
	})
}

func newPkMemberFromParam(orgId string, pkBytes []byte, role protocol.Role,
	hashType string) (*pkMember, error) {

	hash, ok := bccrypto.HashAlgoMap[hashType]
	if !ok {
		return nil, fmt.Errorf("sign failed: unsupport hash type")
	}

	var pkMem pkMember
	pkMem.orgId = orgId
	pkMem.hashType = hashType

	pk, err := asym.PublicKeyFromDER(pkBytes)
	if err != nil {
		return nil, fmt.Errorf("setup pk member failed, err: %s", err.Error())
	}

	pkMem.pk = pk
	pkMem.role = role
	ski, err := commonCert.ComputeSKI(hash, pk.ToStandardKey())

	if err != nil {
		return nil, fmt.Errorf("setup pk member failed, err: %s", err.Error())
	}

	pkMem.uid = hex.EncodeToString(ski)
	pkPem, err := pk.String()
	if err != nil {
		return nil, fmt.Errorf("setup pk member failed, err: %s", err.Error())
	}
	pkMem.id = pkPem

	return &pkMem, nil
}
